2 // IconFamily class implementation
3 // by Troy Stephens, Thomas Schnitzer, David Remahl, Nathan Day and Ben Haller
7 // http://homepage.mac.com/troy_stephens/software/objects/IconFamily/
9 // Problems, shortcomings, and uncertainties that I'm aware of are flagged
10 // with "NOTE:". Please address bug reports, bug fixes, suggestions, etc.
11 // to me at troy_stephens@mac.com
13 // This code is provided as-is, with no warranty, in the hope that it will be
14 // useful. However, it appears to work fine on Mac OS X 10.1.5 and 10.2. :-)
16 //IconFamily depends heavily upon Carbon calls and thus will only work in Mac OS X
17 #ifdef MAC_OS_X_VERSION_10_0
19 #import "IconFamily.h"
20 #import "NSString+CarbonFSSpecCreation.h"
22 @interface IconFamily (Internals)
24 + (NSImage*) resampleImage:(NSImage*)image toIconWidth:(int)width usingImageInterpolation:(NSImageInterpolation)imageInterpolation;
26 + (Handle) get32BitDataFromBitmapImageRep:(NSBitmapImageRep*)bitmapImageRep requiredPixelSize:(int)requiredPixelSize;
28 + (Handle) get8BitDataFromBitmapImageRep:(NSBitmapImageRep*)bitmapImageRep requiredPixelSize:(int)requiredPixelSize;
30 + (Handle) get8BitMaskFromBitmapImageRep:(NSBitmapImageRep*)bitmapImageRep requiredPixelSize:(int)requiredPixelSize;
32 + (Handle) get1BitMaskFromBitmapImageRep:(NSBitmapImageRep*)bitmapImageRep requiredPixelSize:(int)requiredPixelSize;
34 - (BOOL) addResourceType:(OSType)type asResID:(int)resID;
38 @implementation IconFamily
40 + (IconFamily*) iconFamily
42 return [[[IconFamily alloc] init] autorelease];
45 + (IconFamily*) iconFamilyWithContentsOfFile:(NSString*)path
47 return [[[IconFamily alloc] initWithContentsOfFile:path] autorelease];
50 + (IconFamily*) iconFamilyWithIconOfFile:(NSString*)path
52 return [[[IconFamily alloc] initWithIconOfFile:path] autorelease];
55 + (IconFamily*) iconFamilyWithIconFamilyHandle:(IconFamilyHandle)hNewIconFamily
57 return [[[IconFamily alloc] initWithIconFamilyHandle:hNewIconFamily] autorelease];
60 + (IconFamily*) iconFamilyWithSystemIcon:(int)fourByteCode
62 return [[[IconFamily alloc] initWithSystemIcon:fourByteCode] autorelease];
65 + (IconFamily*) iconFamilyWithThumbnailsOfImage:(NSImage*)image
67 return [[[IconFamily alloc] initWithThumbnailsOfImage:image] autorelease];
70 + (IconFamily*) iconFamilyWithThumbnailsOfImage:(NSImage*)image usingImageInterpolation:(NSImageInterpolation)imageInterpolation
72 return [[[IconFamily alloc] initWithThumbnailsOfImage:image usingImageInterpolation:imageInterpolation] autorelease];
75 // This is IconFamily's designated initializer. It creates a new IconFamily that initially has no elements.
77 // The proper way to do this is to simply allocate a zero-sized handle (not to be confused with an empty handle) and assign it to hIconFamily. This technique works on Mac OS X 10.2 as well as on 10.0.x and 10.1.x. Our previous technique of allocating an IconFamily struct with a resourceSize of 0 no longer works as of Mac OS X 10.2.
80 if((self = [super init]))
82 hIconFamily = (IconFamilyHandle) NewHandle( 0 );
83 if (hIconFamily == NULL) {
92 - initWithContentsOfFile:(NSString*)path
100 DisposeHandle( (Handle)hIconFamily );
103 if (![path getFSSpec:&fsSpec createFileIfNecessary:NO]) {
107 result = ReadIconFile( &fsSpec, &hIconFamily );
108 if (result != noErr) {
116 - initWithIconFamilyHandle:(IconFamilyHandle)hNewIconFamily
121 DisposeHandle( (Handle)hIconFamily );
124 // NOTE: Do we have to somehow "retain" the handle
125 // (increment its reference count)?
126 hIconFamily = hNewIconFamily;
131 - initWithIconOfFile:(NSString*)path
143 DisposeHandle( (Handle)hIconFamily );
147 if( ![path getFSSpec:&fileSpec createFileIfNecessary:NO] )
153 result = GetIconRefFromFile(
164 result = IconRefToIconFamily(
166 kSelectorAllAvailableData,
169 if (result != noErr || !hIconFamily)
175 ReleaseIconRef( iconRef );
180 - initWithSystemIcon:(int)fourByteCode
190 DisposeHandle( (Handle)hIconFamily );
194 result = GetIconRef(kOnSystemDisk, kSystemIconsCreator, fourByteCode, &iconRef);
202 result = IconRefToIconFamily(
204 kSelectorAllAvailableData,
207 if (result != noErr || !hIconFamily)
213 ReleaseIconRef( iconRef );
218 - initWithThumbnailsOfImage:(NSImage*)image
220 // The default is to use a high degree of antialiasing, producing a smooth image.
221 return [self initWithThumbnailsOfImage:image usingImageInterpolation:NSImageInterpolationHigh];
224 - initWithThumbnailsOfImage:(NSImage*)image usingImageInterpolation:(NSImageInterpolation)imageInterpolation
226 NSImage* iconImage128x128;
227 NSImage* iconImage32x32;
228 NSImage* iconImage16x16;
229 NSBitmapImageRep* iconBitmap128x128;
230 NSBitmapImageRep* iconBitmap32x32;
231 NSBitmapImageRep* iconBitmap16x16;
232 NSImage* bitmappedIconImage128x128;
234 // Start with a new, empty IconFamily.
239 // Resample the given image to create a 128x128 pixel, 32-bit RGBA
240 // version, and use that as our "thumbnail" (128x128) icon and mask.
242 // Our +resampleImage:toIconWidth:... method, in its present form,
243 // returns an NSImage that contains an NSCacheImageRep, rather than
244 // an NSBitmapImageRep. We convert to an NSBitmapImageRep, so that
245 // our methods can scan the image data, using initWithFocusedViewRect:.
246 iconImage128x128 = [IconFamily resampleImage:image toIconWidth:128 usingImageInterpolation:imageInterpolation];
247 [iconImage128x128 lockFocus];
248 iconBitmap128x128 = [[NSBitmapImageRep alloc] initWithFocusedViewRect:NSMakeRect(0, 0, 128, 128)];
249 [iconImage128x128 unlockFocus];
250 if (iconBitmap128x128) {
251 [self setIconFamilyElement:kThumbnail32BitData fromBitmapImageRep:iconBitmap128x128];
252 [self setIconFamilyElement:kThumbnail8BitMask fromBitmapImageRep:iconBitmap128x128];
255 // Create an NSImage with the iconBitmap128x128 NSBitmapImageRep, that we
256 // can resample to create the smaller icon family elements. (This is
257 // most likely more efficient than resampling from the original image again,
258 // particularly if it is large. It produces a slightly different result, but
259 // the difference is minor and should not be objectionable...)
260 bitmappedIconImage128x128 = [[NSImage alloc] initWithSize:NSMakeSize(128,128)];
261 [bitmappedIconImage128x128 addRepresentation:iconBitmap128x128];
263 // Resample the 128x128 image to create a 32x32 pixel, 32-bit RGBA version,
264 // and use that as our "large" (32x32) icon and 8-bit mask.
265 iconImage32x32 = [IconFamily resampleImage:bitmappedIconImage128x128 toIconWidth:32 usingImageInterpolation:imageInterpolation];
266 [iconImage32x32 lockFocus];
267 iconBitmap32x32 = [[NSBitmapImageRep alloc] initWithFocusedViewRect:NSMakeRect(0, 0, 32, 32)];
268 [iconImage32x32 unlockFocus];
269 if (iconBitmap32x32) {
270 [self setIconFamilyElement:kLarge32BitData fromBitmapImageRep:iconBitmap32x32];
271 [self setIconFamilyElement:kLarge8BitData fromBitmapImageRep:iconBitmap32x32];
272 [self setIconFamilyElement:kLarge8BitMask fromBitmapImageRep:iconBitmap32x32];
273 [self setIconFamilyElement:kLarge1BitMask fromBitmapImageRep:iconBitmap32x32];
276 // Resample the 128x128 image to create a 16x16 pixel, 32-bit RGBA version,
277 // and use that as our "small" (16x16) icon and 8-bit mask.
278 iconImage16x16 = [IconFamily resampleImage:bitmappedIconImage128x128 toIconWidth:16 usingImageInterpolation:imageInterpolation];
279 [iconImage16x16 lockFocus];
280 iconBitmap16x16 = [[NSBitmapImageRep alloc] initWithFocusedViewRect:NSMakeRect(0, 0, 16, 16)];
281 [iconImage16x16 unlockFocus];
282 if (iconBitmap16x16) {
283 [self setIconFamilyElement:kSmall32BitData fromBitmapImageRep:iconBitmap16x16];
284 [self setIconFamilyElement:kSmall8BitData fromBitmapImageRep:iconBitmap16x16];
285 [self setIconFamilyElement:kSmall8BitMask fromBitmapImageRep:iconBitmap16x16];
286 [self setIconFamilyElement:kSmall1BitMask fromBitmapImageRep:iconBitmap16x16];
289 // Release all of the images that we created and no longer need.
290 [bitmappedIconImage128x128 release];
291 [iconBitmap128x128 release];
292 [iconBitmap32x32 release];
293 [iconBitmap16x16 release];
295 // Return the new icon family!
301 DisposeHandle( (Handle)hIconFamily );
305 - (NSBitmapImageRep*) bitmapImageRepWithAlphaForIconFamilyElement:(OSType)elementType;
307 NSBitmapImageRep* bitmapImageRep;
309 Handle hRawBitmapData;
311 OSType maskElementType;
313 unsigned long* pRawBitmapData;
314 unsigned long* pRawBitmapDataEnd;
316 unsigned char* pBitmapImageRepBitmapData;
318 // Make sure elementType is a valid type that we know how to handle, and
319 // figure out the dimensions and bit depth of the bitmap for that type.
320 switch (elementType) {
321 // 'it32' 128x128 32-bit RGB image
322 case kThumbnail32BitData:
323 maskElementType = kThumbnail8BitMask;
327 // 'il32' 32x32 32-bit RGB image
328 case kLarge32BitData:
329 maskElementType = kLarge8BitMask;
333 // 'is32' 16x16 32-bit RGB image
334 case kSmall32BitData:
335 maskElementType = kSmall8BitMask;
343 // Get the raw, uncompressed bitmap data for the requested element.
344 hRawBitmapData = NewHandle( pixelsWide * pixelsWide * 4 );
345 result = GetIconFamilyData( hIconFamily, elementType, hRawBitmapData );
346 if (result != noErr) {
347 DisposeHandle( hRawBitmapData );
351 // Get the corresponding raw, uncompressed 8-bit mask data.
352 hRawMaskData = NewHandle( pixelsWide * pixelsWide );
353 result = GetIconFamilyData( hIconFamily, maskElementType, hRawMaskData );
354 if (result != noErr) {
355 DisposeHandle( hRawMaskData );
359 // The retrieved raw bitmap data is stored at 32 bits per pixel: 3 bytes
360 // for the RGB color of each pixel, plus an extra unused byte. We can
361 // therefore fold the mask data into the color data in-place (though
362 // getting the proper byte ordering requires some bit-shifting).
363 HLock( hRawBitmapData );
364 pRawBitmapData = (unsigned long*) *hRawBitmapData;
365 pRawBitmapDataEnd = pRawBitmapData + pixelsWide * pixelsWide;
367 HLock( hRawMaskData );
368 pRawMaskData = *hRawMaskData;
369 while (pRawBitmapData < pRawBitmapDataEnd) {
370 *pRawBitmapData = (*pRawBitmapData << 8) | *pRawMaskData++;
373 HUnlock( hRawMaskData );
375 while (pRawBitmapData < pRawBitmapDataEnd) {
376 *pRawBitmapData = (*pRawBitmapData << 8) | 0xff;
381 // Create a new NSBitmapImageRep with the given bitmap data. Note that
382 // when creating the NSBitmapImageRep we pass in NULL for the "planes"
383 // parameter. This causes the new NSBitmapImageRep to allocate its own
384 // buffer for the bitmap data (which it will own and release when the
385 // NSBitmapImageRep is released), rather than referencing the bitmap
386 // data we pass in (which will soon disappear when we call
387 // DisposeHandle() below!). (See the NSBitmapImageRep documentation for
388 // the -initWithBitmapDataPlanes:... method, where this is explained.)
390 // Once we have the new NSBitmapImageRep, we get a pointer to its
391 // bitmapData and copy our bitmap data in.
392 bitmapImageRep = [[[NSBitmapImageRep alloc]
393 initWithBitmapDataPlanes:NULL
394 pixelsWide:pixelsWide
395 pixelsHigh:pixelsWide
400 colorSpaceName:NSDeviceRGBColorSpace // NOTE: is this right?
402 bitsPerPixel:0] autorelease];
403 pBitmapImageRepBitmapData = [bitmapImageRep bitmapData];
404 if (pBitmapImageRepBitmapData) {
405 memcpy( pBitmapImageRepBitmapData, *hRawBitmapData,
406 pixelsWide * pixelsWide * 4 );
408 HUnlock( hRawBitmapData );
410 // Free the retrieved raw data.
411 DisposeHandle( hRawBitmapData );
413 DisposeHandle( hRawMaskData );
415 // Return nil if the NSBitmapImageRep didn't give us a buffer to copy into.
416 if (pBitmapImageRepBitmapData == NULL)
419 // Return the new NSBitmapImageRep.
420 return bitmapImageRep;
423 - (NSImage*) imageWithAllReps
425 NSImage* image = NULL;
426 image = [[[NSImage alloc] initWithData:[NSData dataWithBytes:*hIconFamily length:GetHandleSize((Handle)hIconFamily)]] autorelease];
430 //investigate optimisations (dataWithBytesNoCopy:length: for example...)
433 - (BOOL) setIconFamilyElement:(OSType)elementType fromBitmapImageRep:(NSBitmapImageRep*)bitmapImageRep
435 Handle hRawData = NULL;
438 switch (elementType) {
439 // 'it32' 128x128 32-bit RGB image
440 case kThumbnail32BitData:
441 hRawData = [IconFamily get32BitDataFromBitmapImageRep:bitmapImageRep requiredPixelSize:128];
444 // 't8mk' 128x128 8-bit alpha mask
445 case kThumbnail8BitMask:
446 hRawData = [IconFamily get8BitMaskFromBitmapImageRep:bitmapImageRep requiredPixelSize:128];
449 // 'il32' 32x32 32-bit RGB image
450 case kLarge32BitData:
451 hRawData = [IconFamily get32BitDataFromBitmapImageRep:bitmapImageRep requiredPixelSize:32];
454 // 'l8mk' 32x32 8-bit alpha mask
456 hRawData = [IconFamily get8BitMaskFromBitmapImageRep:bitmapImageRep requiredPixelSize:32];
459 // 'ICN#' 32x32 1-bit alpha mask
461 hRawData = [IconFamily get1BitMaskFromBitmapImageRep:bitmapImageRep requiredPixelSize:32];
464 // 'icl8' 32x32 8-bit indexed image data
466 hRawData = [IconFamily get8BitDataFromBitmapImageRep:bitmapImageRep requiredPixelSize:32];
469 // 'is32' 16x16 32-bit RGB image
470 case kSmall32BitData:
471 hRawData = [IconFamily get32BitDataFromBitmapImageRep:bitmapImageRep requiredPixelSize:16];
474 // 's8mk' 16x16 8-bit alpha mask
476 hRawData = [IconFamily get8BitMaskFromBitmapImageRep:bitmapImageRep requiredPixelSize:16];
479 // 'ics#' 16x16 1-bit alpha mask
481 hRawData = [IconFamily get1BitMaskFromBitmapImageRep:bitmapImageRep requiredPixelSize:16];
484 // 'ics8' 16x16 8-bit indexed image data
486 hRawData = [IconFamily get8BitDataFromBitmapImageRep:bitmapImageRep requiredPixelSize:16];
493 if (hRawData == NULL)
495 NSLog(@"Null data returned to setIconFamilyElement:fromBitmapImageRep:");
499 result = SetIconFamilyData( hIconFamily, elementType, hRawData );
500 DisposeHandle( hRawData );
504 NSLog(@"SetIconFamilyData() returned error %d", result);
511 - (BOOL) setAsCustomIconForFile:(NSString*)path
513 return( [self setAsCustomIconForFile:path withCompatibility:NO] );
516 - (BOOL) setAsCustomIconForFile:(NSString*)path withCompatibility:(BOOL)compat
518 FSSpec targetFileFSSpec;
519 FSRef targetFileFSRef;
520 FSRef parentDirectoryFSRef;
524 Handle hExistingCustomIcon;
525 Handle hIconFamilyCopy;
526 NSDictionary *fileAttributes;
527 OSType existingType = kUnknownType, existingCreator = kUnknownType;
529 // Get an FSRef and an FSSpec for the target file, and an FSRef for its parent directory that we can use in the FNNotify() call below.
530 if (![path getFSRef:&targetFileFSRef createFileIfNecessary:NO])
532 result = FSGetCatalogInfo( &targetFileFSRef, kFSCatInfoNone, NULL, NULL, &targetFileFSSpec, &parentDirectoryFSRef );
536 // Get the file's type and creator codes.
537 fileAttributes = [[NSFileManager defaultManager] fileAttributesAtPath:path traverseLink:NO];
540 existingType = [fileAttributes fileHFSTypeCode];
541 existingCreator = [fileAttributes fileHFSCreatorCode];
544 // Make sure the file has a resource fork that we can open. (Although
545 // this sounds like it would clobber an existing resource fork, the Carbon
546 // Resource Manager docs for this function say that's not the case. If
547 // the file already has a resource fork, we receive a result code of
548 // dupFNErr, which is not really an error per se, but just a notification
549 // to us that creating a new resource fork for the file was not necessary.)
550 FSpCreateResFile( &targetFileFSSpec, existingCreator, existingType, smRoman );
552 if (!(result == noErr || result == dupFNErr))
555 // Open the file's resource fork.
556 file = FSpOpenResFile( &targetFileFSSpec, fsRdWrPerm );
560 // Make a copy of the icon family data to pass to AddResource().
561 // (AddResource() takes ownership of the handle we pass in; after the
562 // CloseResFile() call its master pointer will be set to 0xffffffff.
563 // We want to keep the icon family data, so we make a copy.)
564 // HandToHand() returns the handle of the copy in hIconFamily.
565 hIconFamilyCopy = (Handle) hIconFamily;
566 result = HandToHand( &hIconFamilyCopy );
567 if (result != noErr) {
568 CloseResFile( file );
572 // Remove the file's existing kCustomIconResource of type kIconFamilyType
574 hExistingCustomIcon = GetResource( kIconFamilyType, kCustomIconResource );
575 if( hExistingCustomIcon )
576 RemoveResource( hExistingCustomIcon );
578 // Now add our icon family as the file's new custom icon.
579 AddResource( (Handle)hIconFamilyCopy, kIconFamilyType,
580 kCustomIconResource, "\p");
581 if (ResError() != noErr) {
582 CloseResFile( file );
588 [self addResourceType:kLarge8BitData asResID:kCustomIconResource];
589 [self addResourceType:kLarge1BitMask asResID:kCustomIconResource];
590 [self addResourceType:kSmall8BitData asResID:kCustomIconResource];
591 [self addResourceType:kSmall1BitMask asResID:kCustomIconResource];
594 // Close the file's resource fork, flushing the resource map and new icon
596 CloseResFile( file );
597 if (ResError() != noErr)
600 // Now we need to set the file's Finder info so the Finder will know that
601 // it has a custom icon. Start by getting the file's current finder info:
602 result = FSpGetFInfo( &targetFileFSSpec, &finderInfo );
606 // Set the kHasCustomIcon flag, and clear the kHasBeenInited flag.
608 // From Apple's "CustomIcon" code sample:
609 // "set bit 10 (has custom icon) and unset the inited flag
610 // kHasBeenInited is 0x0100 so the mask will be 0xFEFF:"
611 // finderInfo.fdFlags = 0xFEFF & (finderInfo.fdFlags | kHasCustomIcon ) ;
612 finderInfo.fdFlags = (finderInfo.fdFlags | kHasCustomIcon ) & ~kHasBeenInited;
614 // Now write the Finder info back.
615 result = FSpSetFInfo( &targetFileFSSpec, &finderInfo );
619 // Notify the system that the directory containing the file has changed, to give Finder the chance to find out about the file's new custom icon.
620 result = FNNotify( &parentDirectoryFSRef, kFNDirectoryModifiedMessage, kNilOptions );
627 + (BOOL) removeCustomIconFromFile:(NSString*)path
629 FSSpec targetFileFSSpec;
630 FSRef targetFileFSRef;
631 FSRef parentDirectoryFSRef;
635 Handle hExistingCustomIcon;
637 // Get an FSRef and an FSSpec for the target file, and an FSRef for its parent directory that we can use in the FNNotify() call below.
638 if (![path getFSRef:&targetFileFSRef createFileIfNecessary:NO])
640 result = FSGetCatalogInfo( &targetFileFSRef, kFSCatInfoNone, NULL, NULL, &targetFileFSSpec, &parentDirectoryFSRef );
644 // Open the file's resource fork, if it has one.
645 file = FSpOpenResFile( &targetFileFSSpec, fsRdWrPerm );
649 // Remove the file's existing kCustomIconResource of type kIconFamilyType
651 hExistingCustomIcon = GetResource( kIconFamilyType, kCustomIconResource );
652 if( hExistingCustomIcon )
653 RemoveResource( hExistingCustomIcon );
655 // Close the file's resource fork, flushing the resource map out to disk.
656 CloseResFile( file );
657 if (ResError() != noErr)
660 // Now we need to set the file's Finder info so the Finder will know that
661 // it has no custom icon. Start by getting the file's current finder info:
662 result = FSpGetFInfo( &targetFileFSSpec, &finderInfo );
666 // Clear the kHasCustomIcon flag and the kHasBeenInited flag.
667 finderInfo.fdFlags = finderInfo.fdFlags & ~(kHasCustomIcon | kHasBeenInited);
669 // Now write the Finder info back.
670 result = FSpSetFInfo( &targetFileFSSpec, &finderInfo );
674 // Notify the system that the directory containing the file has changed, to give Finder the chance to find out about the file's new custom icon.
675 result = FNNotify( &parentDirectoryFSRef, kFNDirectoryModifiedMessage, kNilOptions );
682 - (BOOL) setAsCustomIconForDirectory:(NSString*)path
684 return [self setAsCustomIconForDirectory:path withCompatibility:NO];
687 - (BOOL) setAsCustomIconForDirectory:(NSString*)path withCompatibility:(BOOL)compat
689 NSFileManager *fm = [NSFileManager defaultManager];
692 NSString *iconrPath = [path stringByAppendingPathComponent:@"Icon\r"];
693 FSSpec targetFileFSSpec, targetFolderFSSpec;
694 FSRef targetFolderFSRef;
698 FSCatalogInfo catInfo;
699 Handle hExistingCustomIcon;
700 Handle hIconFamilyCopy;
702 exists = [fm fileExistsAtPath:path isDirectory:&isDir];
704 if( !isDir || !exists )
707 if( [fm fileExistsAtPath:iconrPath] )
709 if( ![fm removeFileAtPath:iconrPath handler:nil] )
713 if (![iconrPath getFSSpec:&targetFileFSSpec createFileIfNecessary:YES])
716 if( ![path getFSSpec:&targetFolderFSSpec createFileIfNecessary:YES] )
719 if( ![path getFSRef:&targetFolderFSRef createFileIfNecessary:NO] )
722 // Make sure the file has a resource fork that we can open. (Although
723 // this sounds like it would clobber an existing resource fork, the Carbon
724 // Resource Manager docs for this function say that's not the case.)
725 FSpCreateResFile( &targetFileFSSpec, kUnknownType, kUnknownType, smRoman );
726 if (ResError() != noErr)
729 // Open the file's resource fork.
730 file = FSpOpenResFile( &targetFileFSSpec, fsRdWrPerm );
734 // Make a copy of the icon family data to pass to AddResource().
735 // (AddResource() takes ownership of the handle we pass in; after the
736 // CloseResFile() call its master pointer will be set to 0xffffffff.
737 // We want to keep the icon family data, so we make a copy.)
738 // HandToHand() returns the handle of the copy in hIconFamily.
739 hIconFamilyCopy = (Handle) hIconFamily;
740 result = HandToHand( &hIconFamilyCopy );
741 if (result != noErr) {
742 CloseResFile( file );
746 // Remove the file's existing kCustomIconResource of type kIconFamilyType
748 hExistingCustomIcon = GetResource( kIconFamilyType, kCustomIconResource );
749 if( hExistingCustomIcon )
750 RemoveResource( hExistingCustomIcon );
752 // Now add our icon family as the file's new custom icon.
753 AddResource( (Handle)hIconFamilyCopy, kIconFamilyType,
754 kCustomIconResource, "\p");
756 if (ResError() != noErr) {
757 CloseResFile( file );
763 [self addResourceType:kLarge8BitData asResID:kCustomIconResource];
764 [self addResourceType:kLarge1BitMask asResID:kCustomIconResource];
765 [self addResourceType:kSmall8BitData asResID:kCustomIconResource];
766 [self addResourceType:kSmall1BitMask asResID:kCustomIconResource];
769 // Close the file's resource fork, flushing the resource map and new icon
771 CloseResFile( file );
772 if (ResError() != noErr)
775 // Make folder icon file invisible
776 result = FSpGetFInfo( &targetFileFSSpec, &finderInfo );
779 finderInfo.fdFlags = (finderInfo.fdFlags | kIsInvisible ) & ~kHasBeenInited;
780 // And write info back
781 result = FSpSetFInfo( &targetFileFSSpec, &finderInfo );
785 result = FSGetCatalogInfo( &targetFolderFSRef,
786 kFSCatInfoFinderInfo,
787 &catInfo, nil, nil, nil);
788 if( result != noErr )
791 ((DInfo*)catInfo.finderInfo)->frFlags = ( ((DInfo*)catInfo.finderInfo)->frFlags | kHasCustomIcon ) & ~kHasBeenInited;
793 FSSetCatalogInfo( &targetFolderFSRef,
794 kFSCatInfoFinderInfo,
796 if( result != noErr )
799 // Notify the system that the target directory has changed, to give Finder the chance to find out about its new custom icon.
800 result = FNNotify( &targetFolderFSRef, kFNDirectoryModifiedMessage, kNilOptions );
807 /*- (BOOL) writeToFile:(NSString*)path
812 if (![path getFSSpec:&fsSpec createFileIfNecessary:YES])
814 result = WriteIconFile( hIconFamily, &fsSpec );
819 } This method has a problem with files not representable as an FSSpec.*/
821 - (BOOL) writeToFile:(NSString*)path
823 NSData* iconData = NULL;
825 HLock((Handle)hIconFamily);
827 iconData = [NSData dataWithBytes:*hIconFamily length:GetHandleSize((Handle)hIconFamily)];
828 [iconData writeToFile:path atomically:NO];
830 HUnlock((Handle)hIconFamily);
837 @implementation IconFamily (Internals)
839 + (NSImage*) resampleImage:(NSImage*)image toIconWidth:(int)iconWidth usingImageInterpolation:(NSImageInterpolation)imageInterpolation
841 NSGraphicsContext* graphicsContext;
842 BOOL wasAntialiasing;
843 NSImageInterpolation previousImageInterpolation;
844 NSImage* newImage = nil;
845 // NSBitmapImageRep* newBitmapImageRep;
846 // unsigned char* bitmapData;
847 // NSImageRep* originalImageRep;
848 NSImage* workingImage;
849 NSImageRep* workingImageRep;
850 NSSize size, pixelSize, newSize;
854 // Create a working copy of the image and scale its size down to fit in
855 // the square area of the icon.
857 // It seems like there should be a more memory-efficient alternative to
858 // first duplicating the entire original image, but I don't know what it
859 // is. We need to change some properties ("size" and "scalesWhenResized")
860 // of the original image, but we shouldn't change the original, so a copy
862 workingImage = [image copyWithZone:[image zone]];
863 [workingImage setScalesWhenResized:YES];
864 size = [workingImage size];
865 workingImageRep = [workingImage bestRepresentationForDevice:nil];
866 if ([workingImageRep isKindOfClass:[NSBitmapImageRep class]]) {
867 pixelSize.width = [workingImageRep pixelsWide];
868 pixelSize.height = [workingImageRep pixelsHigh];
869 if (!NSEqualSizes( size, pixelSize )) {
870 [workingImage setSize:pixelSize];
871 [workingImageRep setSize:pixelSize];
875 if (size.width >= size.height) {
876 newSize.width = iconWidth;
877 newSize.height = floor( (float) iconWidth * size.height / size.width + 0.5 );
879 newSize.height = iconWidth;
880 newSize.width = floor( (float) iconWidth * size.width / size.height + 0.5 );
882 [workingImage setSize:newSize];
884 #if 1 // This is the way that works. It gives the newImage an NSCachedImageRep.
887 // Create a new image the size of the icon, and clear it to transparent.
888 newImage = [[NSImage alloc] initWithSize:NSMakeSize(iconWidth,iconWidth)];
889 [newImage lockFocus];
890 iconRect.origin.x = iconRect.origin.y = 0;
891 iconRect.size.width = iconRect.size.height = iconWidth;
892 [[NSColor clearColor] set];
893 NSRectFill( iconRect );
895 // Set current graphics context to use antialiasing and high-quality
897 graphicsContext = [NSGraphicsContext currentContext];
898 wasAntialiasing = [graphicsContext shouldAntialias];
899 previousImageInterpolation = [graphicsContext imageInterpolation];
900 [graphicsContext setShouldAntialias:YES];
901 [graphicsContext setImageInterpolation:imageInterpolation];
903 // Composite the working image into the icon bitmap, centered.
904 targetRect.origin.x = ((float)iconWidth - newSize.width ) / 2.0;
905 targetRect.origin.y = ((float)iconWidth - newSize.height) / 2.0;
906 targetRect.size.width = newSize.width;
907 targetRect.size.height = newSize.height;
908 [workingImageRep drawInRect:targetRect];
910 // Restore previous graphics context settings.
911 [graphicsContext setShouldAntialias:wasAntialiasing];
912 [graphicsContext setImageInterpolation:previousImageInterpolation];
914 [newImage unlockFocus];
916 [workingImage release];
918 #else // This was an attempt at explicitly giving the NSImage an NSBitmapImageRep
919 // and drawing to that NSBitmapImageRep. It doesn't work. (See comments
920 // in -initWithThumbnailsOfImage:)
922 // // Create a new 32-bit RGBA bitmap that is width x width pixels.
923 originalImageRep = [image bestRepresentationForDevice:nil];
924 newImage = [[NSImage alloc] initWithSize:NSMakeSize(iconWidth,iconWidth)];
925 [newImage setDataRetained:YES];
926 // [newImage setCachedSeparately:YES];
927 newBitmapImageRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
932 bitsPerSample:[originalImageRep bitsPerSample]
933 samplesPerPixel:[(NSBitmapImageRep*)originalImageRep samplesPerPixel]
934 hasAlpha:[originalImageRep hasAlpha]
936 colorSpaceName:[originalImageRep colorSpaceName]
939 [newImage addRepresentation:newBitmapImageRep];
940 [newImage setScalesWhenResized:YES];
941 [newBitmapImageRep release];
942 // bitmapData = [newBitmapImageRep bitmapData];
944 // memset( bitmapData, 128, iconWidth * iconWidth * 4 );
945 // Copy the original image into the new bitmap, rescaling it to fit.
946 // [newImage lockFocus];
947 [newImage lockFocusOnRepresentation:newBitmapImageRep];
948 // [image compositeToPoint:NSZeroPoint operation:NSCompositeSourceOver];
949 // iconRect.origin.x = iconRect.origin.y = 0;
950 // iconRect.size.width = iconRect.size.height = iconWidth;
951 // [[NSColor clearColor] set];
952 // NSRectFill( iconRect );
953 [workingImage compositeToPoint:NSZeroPoint operation:NSCompositeSourceOver];
954 [newImage unlockFocus];
956 [workingImage release];
959 // Return the new image!
960 return [newImage autorelease];
963 + (Handle) get32BitDataFromBitmapImageRep:(NSBitmapImageRep*)bitmapImageRep requiredPixelSize:(int)requiredPixelSize
971 unsigned char alphaByte;
974 // Get information about the bitmapImageRep.
975 int pixelsWide = [bitmapImageRep pixelsWide];
976 int pixelsHigh = [bitmapImageRep pixelsHigh];
977 int bitsPerSample = [bitmapImageRep bitsPerSample];
978 int samplesPerPixel = [bitmapImageRep samplesPerPixel];
979 int bitsPerPixel = [bitmapImageRep bitsPerPixel];
980 // BOOL hasAlpha = [bitmapImageRep hasAlpha];
981 BOOL isPlanar = [bitmapImageRep isPlanar];
982 // int numberOfPlanes = [bitmapImageRep numberOfPlanes];
983 int bytesPerRow = [bitmapImageRep bytesPerRow];
984 // int bytesPerPlane = [bitmapImageRep bytesPerPlane];
985 unsigned char* bitmapData = [bitmapImageRep bitmapData];
987 // Make sure bitmap has the required dimensions.
988 if (pixelsWide != requiredPixelSize || pixelsHigh != requiredPixelSize)
991 // So far, this code only handles non-planar 32-bit RGBA and 24-bit RGB source bitmaps.
992 // This could be made more flexible with some additional programming to accommodate other possible
996 NSLog(@"get32BitDataFromBitmapImageRep:requiredPixelSize: returning NULL due to isPlanar == YES");
999 if (bitsPerSample != 8)
1001 NSLog(@"get32BitDataFromBitmapImageRep:requiredPixelSize: returning NULL due to bitsPerSample == %d", bitsPerSample);
1005 if (((samplesPerPixel == 3) && (bitsPerPixel == 24)) || ((samplesPerPixel == 4) && (bitsPerPixel == 32)))
1007 rawDataSize = pixelsWide * pixelsHigh * 4;
1008 hRawData = NewHandle( rawDataSize );
1009 if (hRawData == NULL)
1011 pRawData = *hRawData;
1016 if (bitsPerPixel == 32) {
1017 for (y = 0; y < pixelsHigh; y++) {
1018 pSrc = bitmapData + y * bytesPerRow;
1019 for (x = 0; x < pixelsWide; x++) {
1020 // Each pixel is 3 bytes of RGB data, followed by 1 byte of
1021 // alpha. The RGB values are premultiplied by the alpha (so
1022 // that Quartz can save time when compositing the bitmap to a
1023 // destination), and we undo this premultiplication (with some
1024 // lossiness unfortunately) when retrieving the bitmap data.
1025 *pDest++ = alphaByte = *(pSrc+3);
1027 oneOverAlpha = 255.0f / (float)alphaByte;
1028 *pDest++ = *(pSrc+0) * oneOverAlpha;
1029 *pDest++ = *(pSrc+1) * oneOverAlpha;
1030 *pDest++ = *(pSrc+2) * oneOverAlpha;
1039 } else if (bitsPerPixel == 24) {
1040 for (y = 0; y < pixelsHigh; y++) {
1041 pSrc = bitmapData + y * bytesPerRow;
1042 for (x = 0; x < pixelsWide; x++) {
1053 NSLog(@"get32BitDataFromBitmapImageRep:requiredPixelSize: returning NULL due to samplesPerPixel == %d, bitsPerPixel == %", samplesPerPixel, bitsPerPixel);
1060 + (Handle) get8BitDataFromBitmapImageRep:(NSBitmapImageRep*)bitmapImageRep requiredPixelSize:(int)requiredPixelSize
1065 unsigned char* pSrc;
1069 // Get information about the bitmapImageRep.
1070 int pixelsWide = [bitmapImageRep pixelsWide];
1071 int pixelsHigh = [bitmapImageRep pixelsHigh];
1072 int bitsPerSample = [bitmapImageRep bitsPerSample];
1073 int samplesPerPixel = [bitmapImageRep samplesPerPixel];
1074 int bitsPerPixel = [bitmapImageRep bitsPerPixel];
1075 BOOL isPlanar = [bitmapImageRep isPlanar];
1076 int bytesPerRow = [bitmapImageRep bytesPerRow];
1077 unsigned char* bitmapData = [bitmapImageRep bitmapData];
1079 // Make sure bitmap has the required dimensions.
1080 if (pixelsWide != requiredPixelSize || pixelsHigh != requiredPixelSize)
1083 // So far, this code only handles non-planar 32-bit RGBA and 24-bit RGB source bitmaps.
1084 // This could be made more flexible with some additional programming...
1087 NSLog(@"get8BitDataFromBitmapImageRep:requiredPixelSize: returning NULL due to isPlanar == YES");
1090 if (bitsPerSample != 8)
1092 NSLog(@"get8BitDataFromBitmapImageRep:requiredPixelSize: returning NULL due to bitsPerSample == %d", bitsPerSample);
1096 if (((samplesPerPixel == 3) && (bitsPerPixel == 24)) || ((samplesPerPixel == 4) && (bitsPerPixel == 32)))
1098 CGDirectPaletteRef cgPal;
1099 CGDeviceColor cgCol;
1101 rawDataSize = pixelsWide * pixelsHigh;
1102 hRawData = NewHandle( rawDataSize );
1103 if (hRawData == NULL)
1105 pRawData = *hRawData;
1107 cgPal = CGPaletteCreateDefaultColorPalette();
1111 if (bitsPerPixel == 32) {
1112 for (y = 0; y < pixelsHigh; y++) {
1113 pSrc = bitmapData + y * bytesPerRow;
1114 for (x = 0; x < pixelsWide; x++) {
1115 cgCol.red = ((float)*(pSrc)) / 255;
1116 cgCol.green = ((float)*(pSrc+1)) / 255;
1117 cgCol.blue = ((float)*(pSrc+2)) / 255;
1119 *pDest++ = CGPaletteGetIndexForColor(cgPal, cgCol);
1124 } else if (bitsPerPixel == 24) {
1125 for (y = 0; y < pixelsHigh; y++) {
1126 pSrc = bitmapData + y * bytesPerRow;
1127 for (x = 0; x < pixelsWide; x++) {
1128 cgCol.red = ((float)*(pSrc)) / 255;
1129 cgCol.green = ((float)*(pSrc+1)) / 255;
1130 cgCol.blue = ((float)*(pSrc+2)) / 255;
1132 *pDest++ = CGPaletteGetIndexForColor(cgPal, cgCol);
1139 CGPaletteRelease(cgPal);
1143 NSLog(@"get8BitDataFromBitmapImageRep:requiredPixelSize: returning NULL due to samplesPerPixel == %d, bitsPerPixel == %", samplesPerPixel, bitsPerPixel);
1150 + (Handle) get8BitMaskFromBitmapImageRep:(NSBitmapImageRep*)bitmapImageRep requiredPixelSize:(int)requiredPixelSize
1155 unsigned char* pSrc;
1159 // Get information about the bitmapImageRep.
1160 int pixelsWide = [bitmapImageRep pixelsWide];
1161 int pixelsHigh = [bitmapImageRep pixelsHigh];
1162 int bitsPerSample = [bitmapImageRep bitsPerSample];
1163 int samplesPerPixel = [bitmapImageRep samplesPerPixel];
1164 int bitsPerPixel = [bitmapImageRep bitsPerPixel];
1165 // BOOL hasAlpha = [bitmapImageRep hasAlpha];
1166 BOOL isPlanar = [bitmapImageRep isPlanar];
1167 // int numberOfPlanes = [bitmapImageRep numberOfPlanes];
1168 int bytesPerRow = [bitmapImageRep bytesPerRow];
1169 // int bytesPerPlane = [bitmapImageRep bytesPerPlane];
1170 unsigned char* bitmapData = [bitmapImageRep bitmapData];
1172 // Make sure bitmap has the required dimensions.
1173 if (pixelsWide != requiredPixelSize || pixelsHigh != requiredPixelSize)
1176 // So far, this code only handles non-planar 32-bit RGBA, 24-bit RGB and 8-bit grayscale source bitmaps.
1177 // This could be made more flexible with some additional programming...
1180 NSLog(@"get8BitMaskFromBitmapImageRep:requiredPixelSize: returning NULL due to isPlanar == YES");
1183 if (bitsPerSample != 8)
1185 NSLog(@"get8BitMaskFromBitmapImageRep:requiredPixelSize: returning NULL due to bitsPerSample == %d", bitsPerSample);
1189 if (((samplesPerPixel == 1) && (bitsPerPixel == 8)) || ((samplesPerPixel == 3) && (bitsPerPixel == 24)) || ((samplesPerPixel == 4) && (bitsPerPixel == 32)))
1191 rawDataSize = pixelsWide * pixelsHigh;
1192 hRawData = NewHandle( rawDataSize );
1193 if (hRawData == NULL)
1195 pRawData = *hRawData;
1200 if (bitsPerPixel == 32) {
1201 for (y = 0; y < pixelsHigh; y++) {
1202 pSrc = bitmapData + y * bytesPerRow;
1203 for (x = 0; x < pixelsWide; x++) {
1209 else if (bitsPerPixel == 24) {
1210 memset( pDest, 255, rawDataSize );
1212 else if (bitsPerPixel == 8) {
1213 for (y = 0; y < pixelsHigh; y++) {
1214 memcpy( pDest, pSrc, pixelsWide );
1215 pSrc += bytesPerRow;
1216 pDest += pixelsWide;
1222 NSLog(@"get8BitMaskFromBitmapImageRep:requiredPixelSize: returning NULL due to samplesPerPixel == %d, bitsPerPixel == %", samplesPerPixel, bitsPerPixel);
1229 // NOTE: This method hasn't been fully tested yet.
1230 + (Handle) get1BitMaskFromBitmapImageRep:(NSBitmapImageRep*)bitmapImageRep requiredPixelSize:(int)requiredPixelSize
1235 unsigned char* pSrc;
1238 unsigned char maskByte;
1240 // Get information about the bitmapImageRep.
1241 int pixelsWide = [bitmapImageRep pixelsWide];
1242 int pixelsHigh = [bitmapImageRep pixelsHigh];
1243 int bitsPerSample = [bitmapImageRep bitsPerSample];
1244 int samplesPerPixel = [bitmapImageRep samplesPerPixel];
1245 int bitsPerPixel = [bitmapImageRep bitsPerPixel];
1246 // BOOL hasAlpha = [bitmapImageRep hasAlpha];
1247 BOOL isPlanar = [bitmapImageRep isPlanar];
1248 // int numberOfPlanes = [bitmapImageRep numberOfPlanes];
1249 int bytesPerRow = [bitmapImageRep bytesPerRow];
1250 // int bytesPerPlane = [bitmapImageRep bytesPerPlane];
1251 unsigned char* bitmapData = [bitmapImageRep bitmapData];
1253 // Make sure bitmap has the required dimensions.
1254 if (pixelsWide != requiredPixelSize || pixelsHigh != requiredPixelSize)
1257 // So far, this code only handles non-planar 32-bit RGBA, 24-bit RGB, 8-bit grayscale, and 1-bit source bitmaps.
1258 // This could be made more flexible with some additional programming...
1261 NSLog(@"get1BitMaskFromBitmapImageRep:requiredPixelSize: returning NULL due to isPlanar == YES");
1265 if (((bitsPerPixel == 1) && (samplesPerPixel == 1) && (bitsPerSample == 1)) || ((bitsPerPixel == 8) && (samplesPerPixel == 1) && (bitsPerSample == 8)) ||
1266 ((bitsPerPixel == 24) && (samplesPerPixel == 3) && (bitsPerSample == 8)) || ((bitsPerPixel == 32) && (samplesPerPixel == 4) && (bitsPerSample == 8)))
1268 rawDataSize = (pixelsWide * pixelsHigh)/4;
1269 hRawData = NewHandle( rawDataSize );
1270 if (hRawData == NULL)
1272 pRawData = *hRawData;
1277 if (bitsPerPixel == 32) {
1278 for (y = 0; y < pixelsHigh; y++) {
1279 pSrc = bitmapData + y * bytesPerRow;
1280 for (x = 0; x < pixelsWide; x += 8) {
1282 maskByte |= (*(unsigned*)pSrc & 0xff) ? 0x80 : 0; pSrc += 4;
1283 maskByte |= (*(unsigned*)pSrc & 0xff) ? 0x40 : 0; pSrc += 4;
1284 maskByte |= (*(unsigned*)pSrc & 0xff) ? 0x20 : 0; pSrc += 4;
1285 maskByte |= (*(unsigned*)pSrc & 0xff) ? 0x10 : 0; pSrc += 4;
1286 maskByte |= (*(unsigned*)pSrc & 0xff) ? 0x08 : 0; pSrc += 4;
1287 maskByte |= (*(unsigned*)pSrc & 0xff) ? 0x04 : 0; pSrc += 4;
1288 maskByte |= (*(unsigned*)pSrc & 0xff) ? 0x02 : 0; pSrc += 4;
1289 maskByte |= (*(unsigned*)pSrc & 0xff) ? 0x01 : 0; pSrc += 4;
1290 *pDest++ = maskByte;
1294 else if (bitsPerPixel == 24) {
1295 memset( pDest, 255, rawDataSize );
1297 else if (bitsPerPixel == 8) {
1298 for (y = 0; y < pixelsHigh; y++) {
1299 pSrc = bitmapData + y * bytesPerRow;
1300 for (x = 0; x < pixelsWide; x += 8) {
1302 maskByte |= *pSrc++ ? 0x80 : 0;
1303 maskByte |= *pSrc++ ? 0x40 : 0;
1304 maskByte |= *pSrc++ ? 0x20 : 0;
1305 maskByte |= *pSrc++ ? 0x10 : 0;
1306 maskByte |= *pSrc++ ? 0x08 : 0;
1307 maskByte |= *pSrc++ ? 0x04 : 0;
1308 maskByte |= *pSrc++ ? 0x02 : 0;
1309 maskByte |= *pSrc++ ? 0x01 : 0;
1310 *pDest++ = maskByte;
1314 else if (bitsPerPixel == 1) {
1315 for (y = 0; y < pixelsHigh; y++) {
1316 memcpy( pDest, pSrc, pixelsWide / 8 );
1317 pDest += pixelsWide / 8;
1318 pSrc += bytesPerRow;
1322 memcpy( pRawData+(pixelsWide*pixelsHigh)/8, pRawData, (pixelsWide*pixelsHigh)/8 );
1326 NSLog(@"get1BitMaskFromBitmapImageRep:requiredPixelSize: returning NULL due to bitsPerPixel == %d, samplesPerPixel== %d, bitsPerSample == %d", bitsPerPixel, samplesPerPixel, bitsPerSample);
1333 - (BOOL) addResourceType:(OSType)type asResID:(int)resID
1335 Handle hIconRes = NewHandle(0);
1338 err = GetIconFamilyData( hIconFamily, type, hIconRes );
1340 if( !GetHandleSize(hIconRes) || err != noErr )
1343 AddResource( hIconRes, type, resID, "\p" );
1350 // Methods for interfacing with the Carbon Scrap Manager (analogous to and
1351 // interoperable with the Cocoa Pasteboard).
1353 @implementation IconFamily (ScrapAdditions)
1355 + (BOOL) canInitWithScrap
1357 ScrapRef scrap = NULL;
1358 ScrapFlavorInfo* scrapInfos = NULL;
1359 UInt32 numInfos = 0;
1363 GetCurrentScrap(&scrap);
1365 GetScrapFlavorCount(scrap,&numInfos);
1366 scrapInfos = malloc( sizeof(ScrapFlavorInfo)*numInfos );
1368 GetScrapFlavorInfoList(scrap, &numInfos, scrapInfos);
1370 for( i=0; i<numInfos; i++ )
1372 if( scrapInfos[i].flavorType == 'icns' )
1381 + (IconFamily*) iconFamilyWithScrap
1383 return [[[IconFamily alloc] initWithScrap] autorelease];
1388 Handle storageMem = NULL;
1392 if((self = [super init]))
1394 GetCurrentScrap(&scrap);
1396 GetScrapFlavorSize( scrap, 'icns', &amountMem );
1398 storageMem = NewHandle(amountMem);
1400 GetScrapFlavorData( scrap, 'icns', &amountMem, *storageMem );
1402 hIconFamily = (IconFamilyHandle)storageMem;
1409 ScrapRef scrap = NULL;
1411 ClearCurrentScrap();
1412 GetCurrentScrap(&scrap);
1414 HLock((Handle)hIconFamily);
1415 PutScrapFlavor( scrap, 'icns', kScrapFlavorMaskNone, GetHandleSize((Handle)hIconFamily), *hIconFamily);
1416 HUnlock((Handle)hIconFamily);